﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.Common;
using NUnit.Core;
using System.Diagnostics;

namespace NUnitForVSTS.NUnitTest
{
    /// <summary>
    /// Event handler when tests are executed
    /// </summary>
    class TestLoaderExecutionEventDispatcher : TestLoaderEventDispatcher
    {
        public TestLoaderExecutionEventDispatcher(Guid testRunId)
            : base(testRunId)
        {
        }

        private StringBuilder _error = new StringBuilder();
        private StringBuilder _stdOut = new StringBuilder();
        private IDictionary<ITestElement, UnitTestResult> _testResults;
        private UnitTestResult _currentTestResult;

        /// <summary>
        /// Executes the test for class.
        /// </summary>
        /// <param name="testClass">The test class.</param>
        /// <returns></returns>
        public IDictionary<ITestElement, UnitTestResult> ExecuteTestForClass(List<UnitTest> testClass)
        {
            _testResults = new Dictionary<ITestElement, UnitTestResult>();
            if (testClass.Count == 0)
            {
                return _testResults;
            }
            CurrentTests = new Dictionary<string, ITestElement>();
            _error = new StringBuilder();
            _stdOut = new StringBuilder();

            LoadAssemblyInLoader(testClass[0].CodeBase);

            #region Create Execution filter and lists
            var executionFilter = new NUnit.Core.Filters.SimpleNameFilter();
            foreach (UnitTest test in testClass)
            {
                string testName = test.FullyQualifiedClassName + "." + test.Name;
                CurrentTests.Add(testName, test);
                executionFilter.Add(testName);
            }
            #endregion

            LoadTest(testClass[0].FullyQualifiedClassName);

            if (AreTests())
            {
                RunTests(executionFilter);
                UnloadTests();
            }
            else
            {
                foreach (UnitTest test in testClass)
                {
                    UnitTestResult notRunnable = ResultFactory.CreateResult(test, TestRunId);
                    notRunnable.Outcome = TestOutcome.NotRunnable;
                    _testResults.Add(test, notRunnable);
                }
            }
            return _testResults;
        }

        /// <summary>
        /// Tests the started.
        /// </summary>
        /// <param name="testName">Name of the test.</param>
        protected override void TestIsStarted(TestName testName)
        {
            try
            {
                ITestElement test = CurrentTests[testName.FullName];
                _currentTestResult = ResultFactory.CreateResult(test, TestRunId);
                _testResults.Add(test, _currentTestResult);
            }
            catch (Exception e)
            {
                int id = AppDomain.CurrentDomain.Id;
                Trace.WriteLine(id);
                WriteErrorSituation(e.Message, e.StackTrace);
            }
        }

        /// <summary>
        /// Run when the run is finished
        /// </summary>
        /// <param name="nunitResult">The nunit result.</param>
        protected override void RunIsFinished(NUnit.Core.TestResult nunitResult)
        {
            base.RunIsFinished(nunitResult);

            try
            {
                if (nunitResult.IsFailure &&
                    (nunitResult.FailureSite == NUnit.Core.FailureSite.TearDown ||
                     nunitResult.FailureSite == NUnit.Core.FailureSite.SetUp))
                {
                    {
                        string stacktrace = null;
                        if (nunitResult.StackTrace != null)
                        {
                            stacktrace = nunitResult.StackTrace;
                        }
                        string message = null;
                        if (nunitResult.Message != null)
                        {
                            message = nunitResult.Message;
                        }
                        WriteErrorSituation(message, stacktrace);
                    }
                }
            }
            catch (Exception e)
            {
                WriteErrorSituation(e.Message, e.StackTrace);
            }
        }
        /// <summary>
        /// Run when the run is finished
        /// </summary>
        /// <param name="exception">The exception.</param>
        protected override void RunFinishedWithError(Exception exception)
        {
            try
            {
                if (exception != null && _error != null)
                {
                    _error.AppendLine(exception.Message);
                    _error.AppendLine(exception.StackTrace);
                }
            }
            catch (Exception e)
            {
                WriteErrorSituation(e.Message, e.StackTrace);
            }
        }

        /// <summary>
        /// Test is finshed
        /// </summary>
        /// <param name="nunitResult">The nunit result.</param>
        protected override void TestIsFinished(NUnit.Core.TestResult nunitResult)
        {
            try
            {
                if (nunitResult.IsFailure)
                {
                    _currentTestResult.Outcome = TestOutcome.Failed;
                }
                else if (nunitResult.IsSuccess)
                {
                    _currentTestResult.Outcome = TestOutcome.Passed;
                }
                else
                {
                    //Version 2.4 this means error, but since 2.5 this is also a failed setup
                    _currentTestResult.Outcome = TestOutcome.Failed;
                }
                _currentTestResult.EndTime = DateTime.Now;
                _currentTestResult.Duration = TimeSpan.FromMilliseconds(nunitResult.Time * 1000);
                _currentTestResult.StartTime = _currentTestResult.EndTime - _currentTestResult.Duration;
                if (nunitResult.StackTrace != null)
                {
                    _currentTestResult.ErrorInfo = new TestResultErrorInfo(nunitResult.Message);
                    _currentTestResult.ErrorStackTrace = nunitResult.StackTrace;
                }
                if (nunitResult.Message != null)
                {
                    _currentTestResult.AddTextMessage(nunitResult.Message);
                }

                _currentTestResult.StdErr = _error.ToString();
                _currentTestResult.StdOut = _stdOut.ToString();
            }
            catch (Exception e)
            {
                WriteErrorSituation(e.Message, e.StackTrace);
            }
        }

        /// <summary>
        /// Writes the error situation.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="stacktrace">The stacktrace.</param>
        private void WriteErrorSituation(string message, string stacktrace)
        {
            if (_testResults != null)
            {
                foreach (var item in _testResults)
                {
                    UnitTestResult testResult = item.Value;
                    testResult.Outcome = TestOutcome.Failed;
                    if (stacktrace != null)
                    {
                        testResult.ErrorInfo = new TestResultErrorInfo(message);
                        testResult.ErrorStackTrace = stacktrace;
                    }
                    if (message != null)
                    {
                        testResult.AddTextMessage(message);
                    }
                }
            }
        }

        /// <summary>
        /// Writes the output to the log.
        /// </summary>
        /// <param name="output">The output.</param>
        protected override void WriteOutput(TestOutput output)
        {
            try
            {
                switch (output.Type)
                {
                    case NUnit.Core.TestOutputType.Error:
                        if (_error != null)
                        {
                            _error.AppendLine(output.Text);
                        }
                        break;
                    default:
                    case NUnit.Core.TestOutputType.Out:
                        if (_stdOut != null)
                        {
                            _stdOut.AppendLine(output.Text);
                        }
                        break;
                    case NUnit.Core.TestOutputType.Trace:
                        if (_currentTestResult != null)
                        {
                            _currentTestResult.TraceInfo.AppendTrace(output.Text);
                        }
                        break;
                }
            }
            catch (Exception e)
            {
                WriteErrorSituation(e.Message, e.StackTrace);
            }
        }
    }
}
